5.6 cat コマンドを作る
concatenate
con cat enate
$ cat a b c > out
code:cat.c
static void do_cat(const char *path);
static void die(const char *s);
code:cat.c
int
main(int argc, char *argv[])
{
int i;
if (argc < 2) {
fprintf(stderr, "%s: file name not given\n", argv0); exit(1);
}
for (i=1; i < argc; i++) {
}
exit(0);
}
if 文
コマンドライン引数をチェック
argc が2以下、つまりコマンドライン引数が1つも指定されていないときに exit(1) する
code:exit.c
void exit(int status);
code:fprintf.c
int fprintf(FILE *stream const char *fmt, ...);
for 文
すべてのコマンドライン引数について、順番に繰り返す
arg[0] はコマンド自身を指す。だからインデックスは 1 から始める
do_cat() を実行する
code:cat.c
static void
do_cat(const char *path)
{
int fd;
int n;
fd = open(path, O_RDONLY);
if (fd < 0) die(path);
for (;;) {
n = read(fd, buf, sizeof buf);
if (n < 0) die(path);
if (n == 0) break;
if (write(STDOUT_FILENO, buf, n) < 0) die(path);
}
if (close(fd) < 0) die(path);
}
もう全然わからん
do_cat()
code:do_cat.c
static void do_cat(const char *path);
do_cat() の引数は、コマンドライン引数で渡されたファイル名
ポインタ変数 path で渡される
C言語で配列は、ポインタで扱われる?(全然わかっていない)
code:example1.c
fd = open(path, O_RDONLY);
if (fd < 0) die(path);
︙
︙
if (close(fd) < 0) die(path);
最初の open() でファイルを開いている
最後に close() でファイルを閉じている
if 文の条件式の中でファイルを閉じている
自分で書いたらこうなる
code:example1.1.c
status = close(fd)
if (status < 0) die(path);
これだと無駄な変数 status を一つ作らないとならない
die() 関数は、後ろの方で出てくる
無限ループの for 文
無限ループ for(;;){ ~ }
「次の内容を永遠に繰り返せ、それは ~ 」
code:example2.c
for (;;) {
n = read(fd, buf, sizeof buf); // read()
if (n < 0) die(path); // ERROR
if (n == 0) break; // EOF
if (write(STDOUT_FILENO, buf, n) < 0) die(path); // write() and ERROR
}
エラー処理を省くと
code:example2-1.c
for (;;) {
n = read(fd, buf, sizeof buf); // read()
if (n == 0) break; // EOF
write(STDOUT_FILENO, buf, n) // write()
}
ファイルオフセット
seek に対応しているファイルでは、read はファイルオフセットから行われ、ファイルオフセットは読み込んだバイト数分だけ進められる。ファイルオフセットがファイル末尾かそれより先の場合は、読み出しは行われず、 read() は 0 を返す。
read() で fd から buf のバイト数だけ読み込んだあと、ファイルオフセットはその分だけ進められる
if 文のチェックを抜けたら write() で標準出力に書き込んで、終端かどうかチェックする
再び read() に戻って、fd のファイルオフセット分の先から 読み込む
終端まで進むと read() は 0を返す
if 分のチェックで break する
break 文
break文は、プログラミング言語のループ制御構造で用いられる文で、最も内側のfor文、while文、do-while文から脱出する。また、C言語のようなswitch文がある言語では、(ループではないが)switch文から脱出する際にも使用される。
ループ中(またはswitch文)の内部で実行されると、ループ全体(またはswitch文)を打ち切って、次の文へと制御を移す。
sizeof 演算子
code:example4.c
// snip
// sinp
read(fd, buf, sizeof buf); // read()
主にCとC++において、sizeofは、データ型の大きさを求める単項の演算子である。sizeofは原則としてコンパイル時計算される演算子で、式もしくは括弧でくくった型指定子を与えるとその大きさをバイト単位で返す。これは組み込みの数値型(整数型や浮動小数点数型)、列挙型、ポインタ型、利用者定義の複合データ型(構造体、共用体、C++のクラス)まで、ほぼ全てのデータ型に対して使用できる。
sizeofが配列型に使用されると、その配列がメモリ上に占める大きさが演算結果となる。配列の大きさは、要素の型の大きさに要素数をかけた値と規定されており、例えば要素型Tについてsizeof (T[8])はsizeof (T) * 8と同じ値になる。
核心について
code:example5.c
n = read(fd, buf, sizeof buf); // read()
if (n < 0) die(path); // ERROR
if (n == 0) break; // EOF
if (write(STDOUT_FILENO, buf, n) < 0) die(path); // write() and ERROR
do_cat() まとめ
for 文の中の read() と write() でストリームからバイト列を少し読み、読んだものをすべて標準出力に書き込む
それを for 文の中で永遠と繰り返す
ただしファイルの最後まで読み込んだらループを抜ける
(エラー動作ははぶく)
for 文の中で読んでいるストリームは do_cat() に渡されたパスを open() したもの
つまりこのストリームは cat コマンドの引数で渡されたファイルと繋がっている
do_cat() のエラー処理
open(), close(), read(), write() は
成功すると 0 以上の整数を返す
失敗すると -1 を返す
open()
ファイルにつながるストリームを作成して、そのファイルディスクリプタを返す(整数値)
ちなみに、シェルが使用する既知のファイルディスクリプタは3つ
標準出力 0
標準出力 1
標準エラー出力 2
失敗すると -1 を返す
close()
ストリームを閉じたら 0 を返す
失敗すると -1 を返す
read()
読み込んだバイト数を返す(整数値)
ファイル終端まで達したときには 0 を返す
失敗すると -1 を返す
write()
書き込んだバイト数を返す(整数値)
失敗すると -1 を返す
グローバル変数 errno
システムコールが失敗したときには失敗したことを示す定数がグローバル変数 errno にセットされる
ERRor Number(No.)
それぞれのエラーは man を見よ
code:cat.c
static void
die(const char *s)
{
perror(s);
exit(1);
}
code:die.c
static void
die(const char *s)
{
perror(s);
exit(1);
}
perror() でエラーを出力
exit() する
perror(3)
code:perror.c
void perror(const char *s);
グローバル変数 errno の値に合わせたエラーメッセージを標準エラー出力に出力する
引数 s が NULL でも空文字列でもないときは s の文字列と ":" を出力して、そのあとにエラーメッセージを出力する
https://gyazo.com/a9bb556e8c88ff4980ece6ddc3fb4c01
https://gyazo.com/3d3dae88decfc01958c1215a2f0e6c4f